// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package rotation

import (
	"fmt"
	"time"

	"github.com/robfig/cron/v3"
)

const (
	PerformedRegistration   = "registration"
	PerformedDeregistration = "deregistration"
)

// RotationOptions is an embeddable struct to capture common rotation
// settings between a Secret and Auth
type RotationOptions struct {
	// Schedule holds the info for the framework.Schedule
	Schedule *RotationSchedule
}

// RotationJob represents the secret part of a response.
type RotationJob struct {
	RotationOptions

	// RotationID is the ID returned to the user to manage this secret.
	// This is generated by Vault core. Any set value will be ignored.
	// For requests, this will always be blank.
	RotationID string `sentinel:""`
	Path       string
	MountPoint string
	Name       string
}

type RotationJobConfigureRequest struct {
	Name             string
	MountPoint       string
	ReqPath          string
	RotationSchedule string
	RotationWindow   int
	RotationPeriod   int
}

type RotationJobDeregisterRequest struct {
	MountPoint string
	ReqPath    string
}

func (s *RotationJob) Validate() error {
	if s.MountPoint == "" {
		return fmt.Errorf("MountPoint is required")
	}

	if s.Path == "" {
		return fmt.Errorf("ReqPath is required")
	}

	if s.Schedule.RotationSchedule == "" && s.Schedule.RotationPeriod == 0 {
		return fmt.Errorf("RotationSchedule or RotationPeriod is required to set up rotation job")
	}

	return nil
}

func newRotationJob(configRequest *RotationJobConfigureRequest) (*RotationJob, error) {
	var cronSc *cron.SpecSchedule
	if configRequest.RotationSchedule != "" {
		var err error
		cronSc, err = DefaultScheduler.Parse(configRequest.RotationSchedule)
		if err != nil {
			return nil, err
		}
	}

	rs := &RotationSchedule{
		Schedule:          cronSc,
		RotationSchedule:  configRequest.RotationSchedule,
		RotationWindow:    time.Duration(configRequest.RotationWindow) * time.Second,
		RotationPeriod:    time.Duration(configRequest.RotationPeriod) * time.Second,
		NextVaultRotation: time.Time{},
		LastVaultRotation: time.Time{},
	}

	return &RotationJob{
		RotationOptions: RotationOptions{
			Schedule: rs,
		},
		MountPoint: configRequest.MountPoint,
		Path:       configRequest.ReqPath,
		Name:       configRequest.Name,
	}, nil
}

// ConfigureRotationJob builds and returns a configured RotationJob for the mount and request with the given schedule.
func ConfigureRotationJob(configRequest *RotationJobConfigureRequest) (*RotationJob, error) {
	rotationJob, err := newRotationJob(configRequest)
	if err != nil {
		return nil, err
	}

	if err := rotationJob.Validate(); err != nil {
		return nil, fmt.Errorf("error validating rotation job: %s", err)
	}

	// Expect rotation job to exist here
	if rotationJob == nil {
		return nil, fmt.Errorf("rotation credential was nil; expected non-nil value")
	}

	return rotationJob, nil
}
